One of the main applications of OpenPNM
is simulating transport phenomena such as Fickian diffusion, advection diffusion, reactive transport, etc. In this example, we will learn how to perform Fickian diffusion on a Cubic
network. The algorithm works fine with every other network type, but for now we want to keep it simple.
In [1]:
import numpy as np
import openpnm as op
%matplotlib inline
np.random.seed(10)
ws = op.Workspace()
ws.settings["loglevel"] = 40
np.set_printoptions(precision=5)
In [2]:
net = op.network.Cubic(shape=[1, 10, 10], spacing=1e-5)
Next, we need to add a geometry to the generated network. A geometry contains information about size of the pores/throats in a network. OpenPNM
has tons of prebuilt geometries that represent the microstructure of different materials such as Toray090 carbon papers, sand stone, electrospun fibers, etc. For now, we stick to a sample geometry called StickAndBall
that assigns random values to pore/throat diameters.
In [3]:
geom = op.geometry.StickAndBall(network=net, pores=net.Ps, throats=net.Ts)
In [4]:
air = op.phases.Air(network=net)
Finally, we need to add a physics. A physics object contains information about the working fluid in the simulation that depend on the geometry of the network. A good example is diffusive conductance, which not only depends on the thermophysical properties of the working fluid, but also depends on the geometry of pores/throats.
In [5]:
phys_air = op.physics.Standard(network=net, phase=air, geometry=geom)
Now that everything's set up, it's time to perform our Fickian diffusion simulation. For this purpose, we need to add the FickianDiffusion
algorithm to our simulation. Here's how we do it:
In [6]:
fd = op.algorithms.FickianDiffusion(network=net, phase=air)
Note that network
and phase
are required parameters for pretty much every algorithm we add, since we need to specify on which network and for which phase do we want to run the algorithm.
In [7]:
inlet = net.pores('left')
outlet = net.pores('right')
fd.set_value_BC(pores=inlet, values=1.0)
fd.set_value_BC(pores=outlet, values=0.0)
set_value_BC
applies the so-called "Dirichlet" boundary condition to the specified pores. Note that unless you want to apply a single value to all of the specified pores (like we just did), you must pass a list (or ndarray
) as the values
parameter.
In [8]:
fd.run()
When an algorithm is successfully run, the results are attached to the same object. To access the results, you need to know the quantity for which the algorithm was solving. For instance, FickianDiffusion
solves for the quantity pore.concentration
, which is somewhat intuitive. However, if you ever forget it, or wanted to manually check the quantity, you can take a look at the algorithm settings
:
In [9]:
print(fd.settings)
Now that we know the quantity for which FickianDiffusion
was solved, let's take a look at the results:
In [10]:
c = fd['pore.concentration']
print(c)
In [11]:
print('Network shape:', net._shape)
c2d = c.reshape((net._shape))
In [12]:
#NBVAL_IGNORE_OUTPUT
import matplotlib.pyplot as plt
plt.imshow(c2d[0,:,:]);
plt.title('Concentration (mol/m$^3$)')
plt.colorbar()
Out[12]:
In [13]:
rate_inlet = fd.rate(pores=inlet)[0]
print(f'Mass flow rate from inlet: {rate_inlet:.5e} mol/s')